//this file is part of eMule
//Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.emule-project.net )
//
//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either
//version 2 of the License, or (at your option) any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.


#include "emule.h"
#include "packets.h"
#include "UDPSocket.h"
#include <netdb.h>
#include <pthread.h>

static pthread_attr_t attr;//=PTHREAD_CREATE_DETACHED;

#ifdef __FreeBSD__
static wxMutex *s_mutexProtectingGetHostByName;

static int
convert (struct hostent *host, struct hostent *result,
       char *buf, int buflen, int *h_errnop)
{
  int len, i;

  if (!buf || !h_errnop) return -1;
  *h_errnop = h_errno;

  *result = *host;
  result->h_name = (char *) buf;
  /* This is the size. */
  len = strlen (host->h_name) + 1;
  if (len > buflen) return -1;
  buflen -= len;
  buf += len;
  strcpy ((char *) result->h_name, host->h_name);

  /* How many aliases and how big the buffer should be? There
     is always a NULL pointer. */
  for (len = sizeof (char *), i = 0; host->h_aliases [i]; i++)
  {
    /* It should be size of (char *) and the length of string
       plus 1. */
    len += strlen (host->h_aliases [i]) + 1 + sizeof (char *);
  }
  if (len > buflen) return -1;
  buflen -= len;

  /* This is an array of char * for h_aliases. */
#ifdef NEED_ALIGNED_ACCESS
  {
      int extra;
      extra = 4 - (((unsigned long) buf) & 3);
      if (extra != 4) {
         if (buflen < extra)
             return -1;
         buf = (char *) buf + extra;
      }
  }
#endif
  result->h_aliases = (char **) buf;
  buf += (i + 1) * sizeof (char *);

  /* We copy the aliases now. */
  for (i = 0; host->h_aliases [i]; i++)
  {
    result->h_aliases [i] = (char *) buf;
    strcpy (result->h_aliases [i], host->h_aliases [i]);
    buf += strlen (host->h_aliases [i]) + 1;
  }
  /* This is the last one */
  result->h_aliases [i] = NULL;

#if BSD >= 43 || defined(h_addr)
  for (len = sizeof (char *), i = 0; host->h_addr_list [i]; i++)
  {
    /* It should be size of (char *) and the length of string
       plus 1. */
    len += host->h_length + sizeof (char *);
  }
  if (len > buflen) return -1;

  /* This is an array of char * for h_addr_list. */
#ifdef NEED_ALIGNED_ACCESS
  {
      int extra;
      extra = 4 - (((unsigned long) buf) & 0x3);
      if (extra != 4) {
         if (buflen < extra)
             return -1;
         buf = ((char *) buf) + extra;
      }
  }
#endif
  result->h_addr_list = (char **) buf;
  buf += (i + 1) * sizeof (char *);

  /* We copy the h_addr_list now. */
  for (i = 0; host->h_addr_list [i]; i++)
  {
    result->h_addr_list [i] = (char *) buf;
    memcpy (result->h_addr_list [i], host->h_addr_list [i], host->h_length);
    buf += host->h_length;
  }
  /* This is the last one */
  result->h_addr_list [i] = NULL;
#else
  len = strlen (host->h_addr) + 1 + sizeof (char *);
  if (len > buflen) return -1;

  result->h_addr = (char *) buf;
  strcpy (result->h_addr, host->h_addr);
#endif
  return 0;
}

struct hostent *
gethostbyname_r (const char *name,
       struct hostent *result, char *buffer, int buflen,
       int *h_errnop)
{
  struct hostent *host;

  s_mutexProtectingGetHostByName->Lock();

  host = gethostbyname (name);
  if (!host ||
       convert (host, result, buffer, buflen, h_errnop) != 0)
  {
    result = NULL;
  }

  s_mutexProtectingGetHostByName->Unlock();
  return result;
}

#endif /* __FreeBSD__ */

#define TM_DNSDONE 17851


class AsyncDNS : public wxThread
{
public:
  AsyncDNS();
  virtual ExitCode Entry();

  wxString ipName;
  CUDPSocket* socket;
};

AsyncDNS::AsyncDNS() : wxThread(wxTHREAD_DETACHED)
{
}

wxThread::ExitCode AsyncDNS::Entry()
{
  struct hostent ret,*result=NULL;
  int errorno=0;
  char dataBuf[512]={0};

#ifdef __FreeBSD__
  result = gethostbyname_r(ipName.GetData(),&ret,dataBuf,sizeof(dataBuf),&errorno); 
#else
  gethostbyname_r(ipName.GetData(),&ret,dataBuf,sizeof(dataBuf),&result,&errorno);
#endif /* __FreeBSD__ */
  if(result) {
    unsigned long addr=*(unsigned long*)ret.h_addr;
    struct sockaddr_in* newsi=(struct sockaddr_in*)malloc(sizeof(struct sockaddr_in));//new struct sockaddr_in;
    newsi->sin_addr.s_addr=addr;

    wxCommandEvent evt(wxEVT_COMMAND_MENU_SELECTED,TM_DNSDONE);
    evt.SetClientData(socket);
    evt.SetInt((int)newsi);
    wxPostEvent(theApp.emuledlg,evt);    
  }
}

#if 0
CUDPSocketWnd::CUDPSocketWnd(){
}

BEGIN_MESSAGE_MAP(CUDPSocketWnd, CWnd)
	ON_MESSAGE(WM_DNSLOOKUPDONE, OnDNSLookupDone)
END_MESSAGE_MAP()

LRESULT CUDPSocketWnd::OnDNSLookupDone(WPARAM wParam,LPARAM lParam){
	m_pOwner->DnsLookupDone(wParam,lParam);
	return true;
};
#endif

IMPLEMENT_DYNAMIC_CLASS(CUDPSocket,wxDatagramSocket)

static wxIPV4address tmpaddress;
#define ID_SOKETTI 7772

CUDPSocket::CUDPSocket(CServerConnect* in_serverconnect,wxIPV4address& address) 
: wxDatagramSocket(address,wxSOCKET_NOWAIT){
  //m_hWndResolveMessage = NULL;
  sendbuffer = 0;
  cur_server = 0;
  serverconnect = in_serverconnect;

	printf("*** UDP socket at %d\n",address.Service());
  SetEventHandler(*theApp.emuledlg,ID_SOKETTI);
  SetNotify(wxSOCKET_INPUT_FLAG);
  Notify(TRUE);

  //DnsTaskHandle = 0;
}

CUDPSocket::~CUDPSocket(){
  SetNotify(0);
  Notify(FALSE);
	if (cur_server)
		delete cur_server;
	if (sendbuffer)
		delete[] sendbuffer;
	//m_udpwnd.DestroyWindow();
}

#if 0
bool  CUDPSocket::Create(){
  //VERIFY( m_udpwnd.CreateEx(0, AfxRegisterWndClass(0),_T("Emule Socket Wnd"),WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL));
  //m_hWndResolveMessage = m_udpwnd.m_hWnd;
  //m_udpwnd.m_pOwner = this;
  return 1; //CAsyncSocket::Create(0,SOCK_DGRAM,FD_READ);
}
#endif

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

void CUDPSocket::OnReceive(int nErrorCode){
	char buffer[5000];
	wxString serverbuffer;
	uint32 port;
	wxIPV4address addr;
	//int32 length = ReceiveFrom(buffer,5000,serverbuffer,port);
	RecvFrom(addr,buffer,5000);
	int32 length=LastCount();
	// strip IP address from wxSockAddress (do not call Hostname(). we do not want DNS)
	struct in_addr addr_in;
	addr_in.s_addr=GAddress_INET_GetHostAddress(addr.GetAddress());
	char* fromIP=inet_ntoa(addr_in);

	if ((uint8)buffer[0] == OP_EDONKEYPROT && length != (-1))
	  //ProcessPacket(buffer+2,length-2,buffer[1],(char*)addr.Hostname().GetData(),addr.Service()); //serverbuffer.GetBuffer(),port);	  
	  ProcessPacket(buffer+2,length-2,buffer[1],fromIP,addr.Service()); //serverbuffer.GetBuffer(),port);
	else if (((uint8)buffer[0] == OP_EMULEPROT) && length != (-1))
	  //ProcessExtPacket(buffer+2,length-2,buffer[1],(char*)addr.Hostname().GetData(),addr.Service()); //serverbuffer.GetBuffer(),port);
	  ProcessExtPacket(buffer+2,length-2,buffer[1],fromIP,addr.Service()); //serverbuffer.GetBuffer(),port);
	
}

bool CUDPSocket::ProcessPacket(char* packet, int16 size, int8 opcode, char* host, uint16 port){
	try{
		CServer* update;
		update = theApp.serverlist->GetServerByAddress( host, port-4 );
		if( update ){
			update->ResetFailedCount();
			theApp.emuledlg->serverwnd->serverlistctrl->RefreshServer( update );
		}
		switch(opcode){
			case OP_GLOBSEARCHRES:{
				theApp.downloadqueue->AddDownDataOverheadOther(size);
				theApp.emuledlg->searchwnd->AddUDPResult(theApp.searchlist->ProcessUDPSearchanswer(packet,size));
				break;
			}
			case OP_GLOBFOUNDSORUCES:{
				theApp.downloadqueue->AddDownDataOverheadOther(size);
				CSafeMemFile* sources = new CSafeMemFile((BYTE*)packet,size);
				uchar fileid[16];
				sources->Read(fileid,16);
				if (CPartFile* file = theApp.downloadqueue->GetFileByID(fileid))
					file->AddSources(sources,inet_addr(host),port);
				else
					theApp.serverlist->CancelUDPBroadcast();
				delete sources;
				break;
			}
 			case OP_GLOBSERVSTATRES:{
				theApp.downloadqueue->AddDownDataOverheadOther(size);
				if( size < 12 || update == NULL )
					return true;
				uint32 challenge;
				memcpy(&challenge,packet,4);
				if( challenge != update->GetChallenge() )
					return true; 
				uint32 cur_user;
				memcpy(&cur_user,packet+4,4);
				uint32 cur_files;
				memcpy(&cur_files,packet+8,4);
				uint32 cur_maxusers = 0;
				if( size >= 16 ){
					memcpy(&cur_maxusers, packet+12,4);
				}
				if( update ){
					update->SetPing( ::GetTickCount() - update->GetLastPinged() );
					update->SetUserCount( cur_user );
					update->SetFileCount( cur_files );
					update->SetMaxUsers( cur_maxusers );
					theApp.emuledlg->serverwnd->serverlistctrl->RefreshServer( update );
				}
				break;
			}

			default:
				theApp.downloadqueue->AddDownDataOverheadOther(size);
				return false;
		}
		return true;
	}
	catch(...){
	  //OUTPUT_DEBUG_TRACE();
		theApp.emuledlg->AddDebugLogLine(false,GetResString(IDS_ERR_UDP_MISCONF));
		return false;
	}
}

bool CUDPSocket::ProcessExtPacket(char* packet, int16 size, int8 opcode, char* host, uint16 port){
	try{
		switch(opcode){
			/*
			case OP_UDPVERIFYUPREQ:{
				ASSERT (size == 6);
				if (size == 6){
					uint32 checkclientip = 0;
					uint16 checkclientport = 0;
					memcpy(&checkclientip,packet,4);
					memcpy(&checkclientport,packet,2);
					if (theApp.clientlist->VerifyUpload(checkclientip,checkclientport)){
						Packet answer(OP_UDPVERIFYUPA,0,OP_EMULEPROT);
						SendTo(answer.GetUDPHeader(),2,port,host);
					}
				}
				break;
			}*/
			case OP_UDPVERIFYUPA:{
				theApp.downloadqueue->AddDownDataOverheadOther(size);
				break;
			}
			default:
				theApp.downloadqueue->AddDownDataOverheadOther(size);
				return false;
		}
		return true;
	}
	catch(...){
	  //OUTPUT_DEBUG_TRACE();
		theApp.emuledlg->AddDebugLogLine(false,GetResString(IDS_ERR_UDPEXT));
		return false;
	}
}

#if 0
void CUDPSocket::AsyncResolveDNS(LPCTSTR lpszHostAddress, UINT nHostPort){
	m_lpszHostAddress = lpszHostAddress;
	m_nHostPort = nHostPort;
	//if (DnsTaskHandle)
	//	WSACancelAsyncRequest(DnsTaskHandle);
	//DnsTaskHandle = NULL;
	// see if we have a ip already
	USES_CONVERSION;
	SOCKADDR_IN sockAddr;
	memset(&sockAddr,0,sizeof(sockAddr));
	LPSTR lpszAscii = T2A((LPTSTR)m_lpszHostAddress);
	sockAddr.sin_family = AF_INET;
	sockAddr.sin_addr.s_addr = inet_addr(lpszAscii);
	sockAddr.sin_port = m_nHostPort; //htons((u_short)m_nHostPort);

	// backup for send socket
	m_SaveAddr = sockAddr;

	if (sockAddr.sin_addr.s_addr == INADDR_NONE){
		/* Resolve hostname "hostname" asynchronously */ 
		memset(DnsHostBuffer, 0, sizeof(DnsHostBuffer));

		DnsTaskHandle = WSAAsyncGetHostByName(
			m_hWndResolveMessage,
			WM_DNSLOOKUPDONE,
			lpszHostAddress,
			DnsHostBuffer,
			MAXGETHOSTSTRUCT);

		if (DnsTaskHandle == 0){
			delete[] sendbuffer;
			sendbuffer = 0;
			delete cur_server;
			cur_server = 0;
#ifdef _DEBUG
			AfxMessageBox("LOOKUPERROR DNSTASKHANDLE = 0");
#endif
		}
	}
	else{
		SendBuffer();
	}
}
#endif

void CUDPSocket::DnsLookupDone(struct sockaddr_in* inaddr) {
  /* An asynchronous database routine completed. */
  if (inaddr==NULL) { /*WSAGETASYNCERROR(lp) != 0){*/
    if (sendbuffer)
      delete[] sendbuffer;
    sendbuffer = 0;
    if (cur_server)
      delete cur_server;
    cur_server = 0;
    return;
  }
  
  if (m_SaveAddr.sin_addr.s_addr == INADDR_NONE){
    m_SaveAddr.sin_addr.s_addr = inaddr->sin_addr.s_addr;
  }
  if (cur_server){
    CServer* update = theApp.serverlist->GetServerByAddress(cur_server->GetAddress(),cur_server->GetPort());
    if (update)
      update->SetID(m_SaveAddr.sin_addr.s_addr);
    SendBuffer();
  }
  if(inaddr) {
    // must free it
    //delete inaddr;
    free(inaddr);
  }
}

// prevent fscking dns queries
class emuleIPV4Address : public wxIPV4address {
public:
  emuleIPV4Address() : wxIPV4address() {};
  virtual bool Hostname(unsigned long addr) {
    return GAddress_INET_SetHostAddress(m_address,addr)==GSOCK_NOERROR;
  };
};

void CUDPSocket::SendBuffer(){
	if(cur_server && sendbuffer){
	  //SendTo(sendbuffer,sendblen,(SOCKADDR*)&m_SaveAddr, sizeof(m_SaveAddr));
	  //wxIPV4address addr;
	  emuleIPV4Address addr;
	  struct in_addr tmpa;
	  tmpa.s_addr=m_SaveAddr.sin_addr.s_addr;
	  addr.Hostname(m_SaveAddr.sin_addr.s_addr);
	  addr.Service(m_SaveAddr.sin_port);
	  // don't send if socket isn't there
	  if(Ok()) SendTo(addr,sendbuffer,sendblen);
	  delete[] sendbuffer;
	  sendbuffer = 0;
	  delete cur_server;
	  cur_server = 0;
	}
}

void CUDPSocket::SendPacket(Packet* packet,CServer* host){
	cur_server = new CServer(host);
	sendbuffer = new char[packet->size+2];
	memcpy(sendbuffer,packet->GetUDPHeader(),2);
	memcpy(sendbuffer+2,packet->pBuffer,packet->size);
	sendblen = packet->size+2;

	// see if we need to dns()
	struct sockaddr_in sockAddr;
	memset(&sockAddr,0,sizeof(sockAddr));
	sockAddr.sin_family=AF_INET;
	sockAddr.sin_addr.s_addr=inet_addr(cur_server->GetAddress());
	sockAddr.sin_port=cur_server->GetPort()+4; //htons(cur_server->GetPort()+4);
	m_SaveAddr=sockAddr;

	if(sockAddr.sin_addr.s_addr==INADDR_NONE) {
#if 0
	  ASYNCDNSINFO* newp=(ASYNCDNSINFO*)malloc(sizeof(ASYNCDNSINFO));
	  struct sigevent sigev;
	  newp->req.ar_name=cur_server->GetAddress();
	  newp->req.ar_service=NULL;
	  newp->req.ar_request=NULL;
	  newp->socket=this;
	  newp->port=cur_server->GetPort()+4;
	  
	  sigev.sigev_notify=SIGEV_THREAD;
	  sigev.sigev_value.sival_ptr=newp;
	  sigev.sigev_notify_function=DNSLookupDone;
	  sigev.sigev_notify_attributes=&attr;
	  
	  static int i=0;
	  if(!i) {
	    pthread_attr_init(&attr);	  
	    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
	    i++;
	  }
	  getaddrinfo_a(GAI_NOWAIT,(struct gaicb**)&newp,1,&sigev);
#endif
	  AsyncDNS* dns=new AsyncDNS();
	  if(dns->Create()!=wxTHREAD_NO_ERROR) {
	    // uh?
	    return;
	  }
	  dns->ipName=cur_server->GetAddress();
	  dns->socket=this;
	  dns->Run();
	} else {
	  SendBuffer();
	}
	//AsyncResolveDNS(cur_server->GetAddress(),cur_server->GetPort()+4);
	//printf("todo.. resolve dns and so on\n");
}
